<template>
  <div
    ref="rootRef"
    class="swp"
    :class="{ 'swp--touch': isDragging }"
    @touchstart.passive="onTouchStart"
    @touchmove="onTouchMove"
    @touchend="onTouchEnd"
    @touchcancel="onTouchEnd"
    @mouseenter="handleMouseEnter"
    @mouseleave="handleMouseLeave"
    tabindex="0"
    role="region"
    aria-roledescription="carousel"
    :aria-label="ariaLabel"
    @keydown.left.prevent="prev"
    @keydown.right.prevent="next"
  >
    <div
      ref="trackRef"
      class="swp__track"
      :style="trackStyle"
      @transitionend="onTransitionEnd"
    >
      <SwiperRender
        v-for="(vnode, i) in renderedVNodes"
        :key="keys[i]"
        :vnode="vnode"
        class="swp__item"
      />
    </div>

    <div v-if="showIndicators && count > 1" class="swp__indicators">
      <button
        v-for="n in count"
        :key="`ind-${n - 1}`"
        class="swp__dot"
        :class="{ 'swp__dot--active': realIndex === n - 1 }"
        @click="goTo(n - 1)"
        :aria-label="`Go to slide ${n}`"
      />
    </div>

    <button
      v-if="showArrows && count > 1"
      class="swp__arrow swp__arrow--prev"
      @click="prev"
      aria-label="Prev"
    >
      <slot name="prev">‹</slot>
    </button>
    <button
      v-if="showArrows && count > 1"
      class="swp__arrow swp__arrow--next"
      @click="next"
      aria-label="Next"
    >
      <slot name="next">›</slot>
    </button>
  </div>
</template>

<script setup lang="ts">
import {
  Comment,
  Fragment,
  type VNode,
  computed,
  nextTick,
  onBeforeUnmount,
  onMounted,
  ref,
  useSlots,
  watch,
} from "vue";
import SwiperRender from "./Render";

interface Props {
  autoplay?: boolean;
  interval?: number;
  loop?: boolean;
  initialIndex?: number;
  duration?: number;
  threshold?: number;
  showIndicators?: boolean;
  showArrows?: boolean;
}

const props = withDefaults(
  defineProps<
    Props & {
      pauseOnHover?: boolean;
      respectReducedMotion?: boolean;
      ariaLabel?: string;
    }
  >(),
  {
    autoplay: true,
    interval: 4000,
    loop: true,
    initialIndex: 0,
    duration: 300,
    threshold: 50,
    showIndicators: true,
    showArrows: false,
    pauseOnHover: true,
    respectReducedMotion: true,
    ariaLabel: "carousel",
  }
);

const slots = useSlots();

function flatted(vnodes: VNode[] = []): VNode[] {
  const res: VNode[] = [];
  vnodes.forEach((v) => {
    if (v == null) return;
    // 过滤注释节点
    if (typeof v.type === "symbol" && v.type.toString().includes("v-cmt"))
      return;
    if (v.type === Comment) return;
    if (v.type === Fragment && Array.isArray(v.children)) {
      res.push(...flatted(v.children as VNode[]));
    } else {
      res.push(v);
    }
  });
  return res;
}

const rawChildren = computed(() =>
  flatted((slots.default?.() as VNode[]) || [])
);
const count = computed(() => rawChildren.value.length);

// 初始位置（SSR 同步计算，避免首屏闪到克隆项）
const initialPosition = computed(() =>
  props.loop && count.value > 1 ? props.initialIndex + 1 : props.initialIndex
);

// 渲染节点：首尾各添加一个克隆节点用于无缝循环
const renderedVNodes = computed(() => {
  if (!props.loop || count.value <= 1) return rawChildren.value;
  const first = rawChildren.value[0];
  const last = rawChildren.value[count.value - 1];
  return [last, ...rawChildren.value, first];
});

const keys = computed(() => renderedVNodes.value.map((_, i) => `swp-key-${i}`));

// 位置索引：指向渲染列表中的索引（包含克隆）
const positionIndex = ref(0);
// 在 setup 阶段就对齐初始位置，确保 SSR/水合一致
positionIndex.value = initialPosition.value;
// 过渡状态
const isTransitioning = ref(false);
// DOM
const trackRef = ref<HTMLElement>();
const rootRef = ref<HTMLElement>();

// 触摸
const isDragging = ref(false);
const startX = ref(0);
const startY = ref(0);
const deltaX = ref(0);

// 实际索引（不含克隆）
const realIndex = computed(() => {
  if (!props.loop || count.value <= 1) return positionIndex.value;
  if (positionIndex.value === 0) return count.value - 1; // 在头部克隆
  if (positionIndex.value === count.value + 1) return 0; // 在尾部克隆
  return positionIndex.value - 1;
});

const trackStyle = computed(() => ({
  transform: `translate3d(-${positionIndex.value * 100}%, 0, 0)`,
  transition: isTransitioning.value
    ? `transform ${props.duration}ms ease`
    : "none",
}));

function setPositionInstant(idx: number) {
  isTransitioning.value = false;
  positionIndex.value = idx;
}

function goTo(index: number) {
  if (count.value === 0) return;
  if (isTransitioning.value) return;
  const target = props.loop && count.value > 1 ? index + 1 : index;
  if (target === positionIndex.value) return;
  isTransitioning.value = true;
  positionIndex.value = Math.max(
    0,
    Math.min(target, props.loop ? count.value + 1 : count.value - 1)
  );
}

function next() {
  if (count.value <= 1) return;
  if (isTransitioning.value) return;
  isTransitioning.value = true;
  if (props.loop && count.value > 1) {
    positionIndex.value += 1;
  } else {
    positionIndex.value = Math.min(positionIndex.value + 1, count.value - 1);
  }
}

function prev() {
  if (count.value <= 1) return;
  if (isTransitioning.value) return;
  isTransitioning.value = true;
  if (props.loop && count.value > 1) {
    positionIndex.value -= 1;
  } else {
    positionIndex.value = Math.max(positionIndex.value - 1, 0);
  }
}

function onTransitionEnd() {
  if (!props.loop || count.value <= 1) {
    isTransitioning.value = false;
    return;
  }
  // 在克隆项上时，瞬移到对应真实项
  if (positionIndex.value === 0) {
    // 头部克隆 -> 跳到最后一个真实项
    isTransitioning.value = false;
    nextTick(() => setPositionInstant(count.value));
  } else if (positionIndex.value === count.value + 1) {
    // 尾部克隆 -> 跳到第一个真实项
    isTransitioning.value = false;
    nextTick(() => setPositionInstant(1));
  } else {
    isTransitioning.value = false;
  }
}

function onTouchStart(e: TouchEvent) {
  if (count.value <= 1) return;
  isDragging.value = true;
  startX.value = e.touches[0]?.clientX || 0;
  startY.value = e.touches[0]?.clientY || 0;
  deltaX.value = 0;
  pause();
}

function onTouchMove(e: TouchEvent) {
  if (!isDragging.value || !rootRef.value || !trackRef.value) return;
  const x = e.touches[0]?.clientX || 0;
  const y = e.touches[0]?.clientY || 0;
  if (Math.abs(x - startX.value) < Math.abs(y - startY.value)) return;
  deltaX.value = x - startX.value;
  const width = rootRef.value.clientWidth || 1;
  const percent = (deltaX.value / width) * 100;
  trackRef.value.style.transition = "none";
  trackRef.value.style.transform = `translateX(-${
    positionIndex.value * 100 - percent
  }%)`;
}

function onTouchEnd() {
  if (!isDragging.value || !trackRef.value) return;
  isDragging.value = false;
  if (Math.abs(deltaX.value) > props.threshold) {
    if (deltaX.value > 0) prev();
    else next();
  } else {
    // 回弹
    trackRef.value.style.transition = `transform ${props.duration}ms ease`;
    trackRef.value.style.transform = `translateX(-${
      positionIndex.value * 100
    }%)`;
  }
  resume();
}

// autoplay
let timer: ReturnType<typeof setInterval> | null = null;
let wasRunningBeforeHide = false;

const shouldAutoplay = computed(() => {
  if (!props.autoplay) return false;
  if (props.respectReducedMotion && typeof window !== "undefined") {
    try {
      return !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    } catch {}
  }
  return true;
});
function start() {
  if (!shouldAutoplay.value || count.value <= 1) return;
  stop();
  timer = setInterval(() => {
    next();
  }, props.interval);
}
function stop() {
  if (timer) {
    clearInterval(timer);
    timer = null;
  }
}
function pause() {
  stop();
}
function resume() {
  if (shouldAutoplay.value) start();
}

function handleMouseEnter() {
  if (props.pauseOnHover) pause();
}
function handleMouseLeave() {
  if (props.pauseOnHover) resume();
}

onMounted(() => {
  start();

  if (typeof document !== "undefined") {
    const visHandler = () => {
      if (document.hidden) {
        wasRunningBeforeHide = !!timer;
        pause();
      } else {
        if (wasRunningBeforeHide) resume();
      }
    };
    document.addEventListener("visibilitychange", visHandler);
    onBeforeUnmount(() => {
      document.removeEventListener("visibilitychange", visHandler);
    });
  }
});

onBeforeUnmount(() => {
  stop();
});

watch(count, () => {
  // 子项变化时重启自动播放并校正位置
  stop();
  positionIndex.value = props.loop && count.value > 1 ? 1 : 0;
  start();
});

// 暴露 API
defineExpose({ next, prev, goTo, pause, resume, start, realIndex });
</script>

<style scoped lang="scss">
.swp {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  touch-action: pan-y pinch-zoom;
}

.swp__track {
  display: flex;
  width: 100%;
  height: 100%;
  will-change: transform;
}

.swp__item {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
}

.swp__indicators {
  position: absolute;
  left: 50%;
  bottom: 0.4rem;
  transform: translateX(-50%);
  display: flex;
  gap: 0.2rem;
  z-index: 5;
}
.swp__dot {
  width: 0.16rem;
  height: 0.16rem;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.4);
  transition: all 0.2s;
}
.swp__dot--active {
  background: #fff;
  width: 0.4rem;
  border-radius: 0.2rem;
}

.swp__arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.35);
  color: #fff;
  z-index: 6;
}
.swp__arrow--prev {
  left: 0.2rem;
}
.swp__arrow--next {
  right: 0.2rem;
}
</style>
